package timing.ukulele.user.controller;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.codec.binary.Base64;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Import;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import timing.ukulele.common.data.ResponseData;
import timing.ukulele.common.dictionary.ThirdPartyTypeEnum;
import timing.ukulele.data.user.view.ThirdPartyUserVO;
import timing.ukulele.data.user.view.UserVO;
import timing.ukulele.facade.user.mvc.ThirdPartyUserControllerFacade;
import timing.ukulele.http.OkHttpManager;
import timing.ukulele.user.config.properties.AppProperties;
import timing.ukulele.user.config.properties.SiteProperties;
import timing.ukulele.user.model.WxAppSessionResponse;
import timing.ukulele.user.model.WxMpMessageXmlModel;
import timing.ukulele.user.model.WxWeixinTokenResponse;
import timing.ukulele.user.persistent.SysThirdPartyUser;
import timing.ukulele.user.service.SysThirdPartyUserService;
import timing.ukulele.user.service.SysUserService;
import timing.ukulele.user.util.AESUtil;
import timing.ukulele.user.util.WeiXinMpUtil;
import timing.ukulele.user.util.WeixinMessageUtil;
import timing.ukulele.web.controller.BaseController;

import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static timing.ukulele.user.config.TopicConfig.WX_MP_CHANNEL;
import static timing.ukulele.user.config.TopicConfig.WX_MP_ROUTING;


@RestController
@Slf4j
@Import({SiteProperties.class, AppProperties.class})
public class ThirdPartyUserController extends BaseController implements ThirdPartyUserControllerFacade {
    final SysUserService userService;
    final SysThirdPartyUserService thirdPartyUserService;
    final WeixinMessageUtil weixinMessageUtil;
    final AppProperties appProperties;
    final SiteProperties siteProperties;
    private final String appCode2SessionUrl;
    private final String weiXinOauthUrl;
    private final AmqpTemplate rabbitTemplate;

    public ThirdPartyUserController(SysUserService userService,
                                    SysThirdPartyUserService thirdPartyUserService, WeixinMessageUtil weixinMessageUtil,
                                    AppProperties appProperties, SiteProperties siteProperties, AmqpTemplate rabbitTemplate) {
        this.userService = userService;
        this.thirdPartyUserService = thirdPartyUserService;
        this.weixinMessageUtil = weixinMessageUtil;
        this.appProperties = appProperties;
        this.siteProperties = siteProperties;
        appCode2SessionUrl = String.format(appProperties.getCode2SessionUrl(), appProperties.getAppid(), appProperties.getAppsecret()) + "&js_code=%s";
        weiXinOauthUrl = String.format(siteProperties.getBaseUrl(), siteProperties.getAppid(), siteProperties.getSecret()) + "&code=%s";
        this.rabbitTemplate = rabbitTemplate;
    }

    @GetMapping("/page/{current}/{size}")
    public ResponseData<IPage<ThirdPartyUserVO>> getPage(@PathVariable(name = "current") int current,
                                                         @PathVariable(name = "size") int size,
                                                         @RequestParam(required = false, value = "username") String username,
                                                         @RequestParam(required = false, value = "userId") Long userId,
                                                         @RequestParam(required = false, value = "unionId") String unionId,
                                                         @RequestParam(required = false, value = "platId") String platId,
                                                         @RequestParam(required = false, value = "platSource") Integer platSource,
                                                         @RequestParam(value = "deleted", required = false) Boolean deleted) {
        if (size == 0) size = 10;
        if (current == 0) current = 1;
        if (platSource != null) {
            if (!ThirdPartyTypeEnum.hasValue(platSource)) {
                return paraErrorResponse(null, "不支持的平台类型");
            }
        }
        IPage<ThirdPartyUserVO> page = this.thirdPartyUserService.getPage(username, userId, unionId, platId, platSource, deleted, current, size);
        return successResponse(page);
    }

    @GetMapping()
    public ResponseData<ThirdPartyUserVO> getById(@RequestParam("id") Long id) {
        SysThirdPartyUser po = this.thirdPartyUserService.getById(id);
        if (po == null)
            return successResponse();
        ThirdPartyUserVO vo = new ThirdPartyUserVO();
        BeanUtils.copyProperties(po, vo);
        return successResponse(vo);
    }

    /**
     * 根据code获取openid
     *
     * @param code
     * @return
     */
    @GetMapping("/wx/weixin/login")
    public ResponseData<String> wxWeixinLogin(@RequestParam("code") String code) {
        if (!StringUtils.hasText(code))
            return paraErrorResponse();
        String url = String.format(weiXinOauthUrl, code);
        Request request = new Request.Builder()
                .url(url).get()
                .build();
        try {
            Response response = OkHttpManager.INSTANCE.build(null).getClient().newCall(request).execute();
            if (response.isSuccessful()) {
                try {
                    String responseBody = response.body().string();
                    WxWeixinTokenResponse model = JSON.parseObject(responseBody, new TypeReference<WxWeixinTokenResponse>() {
                    });
                    if (model != null && model.getOpenid() != null && model.getOpenid().length() > 0)
                        return successResponse(model.getOpenid());
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return errorResponse("获取用户微信标识失败");
    }

    /**
     * 小程序中获取手机号码
     *
     * @param params
     * @return
     */
    @PostMapping("/wx/app/phone")
    public ResponseData<String> wxAppPhone(@RequestBody Map<String, String> params) {
        String code = params.get("code");
        String encryptedData = params.get("encryptedData");
        String iv = params.get("iv");
        if (!(StringUtils.hasText(code) && StringUtils.hasText(encryptedData) && StringUtils.hasText(iv))) {
            return paraErrorResponse();
        }
        // 1. 获取sessionKey
        String sessionKey = null;
        String url = String.format(appCode2SessionUrl, code);
        Request request = new Request.Builder()
                .url(url).get()
                .build();
        try {
            Response response = OkHttpManager.INSTANCE.build(null).getClient().newCall(request).execute();
            if (response.isSuccessful()) {
                try {
                    String responseBody = response.body().string();
                    WxAppSessionResponse model = JSON.parseObject(responseBody, new TypeReference<WxAppSessionResponse>() {
                    });
                    if (model != null)
                        sessionKey = model.getSession_key();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        if (!StringUtils.hasText(sessionKey))
            return failResponse("获取小程序账户信息失败！");
        // 2. 解密
        try {
            AESUtil aes = new AESUtil();
            byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));
            if (null != resultByte && resultByte.length > 0) {
                String phoneNumber = new String(resultByte, StandardCharsets.UTF_8);
                return successResponse(phoneNumber);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return failResponse();
    }

    @PostMapping("/wx/app/phoneRegister")
    public ResponseData<String> wxAppPhoneRegister(@RequestBody Map<String, String> params) {
        String code = params.get("code");
        String encryptedData = params.get("encryptedData");
        String iv = params.get("iv");
        if (!(StringUtils.hasText(code) && StringUtils.hasText(encryptedData) && StringUtils.hasText(iv))) {
            return paraErrorResponse();
        }
        // 1. 获取sessionKey
        String sessionKey = null;
        WxAppSessionResponse model = null;
        String url = String.format(appCode2SessionUrl, code);
        Request request = new Request.Builder()
                .url(url).get()
                .build();
        try {
            Response response = OkHttpManager.INSTANCE.build(null).getClient().newCall(request).execute();
            if (response.isSuccessful()) {
                try {
                    String responseBody = response.body().string();
                    model = JSON.parseObject(responseBody, new TypeReference<>() {
                    });
                    if (model != null)
                        sessionKey = model.getSession_key();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        if (!StringUtils.hasLength(sessionKey))
            return failResponse("获取小程序账户信息失败！");
        // 2. 解密
        try {
            AESUtil aes = new AESUtil();
            log.info("encryptedData:" + encryptedData);
            log.info("sessionKey:" + sessionKey);
            log.info("iv:" + iv);
            byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));
            if (null != resultByte && resultByte.length > 0) {
                String phoneNumber = new String(resultByte, StandardCharsets.UTF_8);
                JSONObject phoneInfo = JSON.parseObject(phoneNumber);
                if (phoneInfo == null)
                    return failResponse(null, "获取手机号失败");
                String purePhoneNumber = phoneInfo.getString("purePhoneNumber");
                if (!StringUtils.hasText(purePhoneNumber)) {
                    return failResponse(null, "获取手机号失败");
                }
                UserVO userVO = new UserVO();
                userVO.setUsername(RandomUtil.randomString(12));
                userVO.setPhone(purePhoneNumber);
                userVO.setPlatId(model.getOpenid());
                userVO.setPlat(ThirdPartyTypeEnum.WX_APP.getValue());
                userVO.setUnionId(model.getUnionid());
                return userService.registerUser(userVO, true);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return failResponse();
    }

    @Override
    public ResponseData<ThirdPartyUserVO> getUserByThirdInfo(String platId, Integer plat) {
        LambdaQueryWrapper<SysThirdPartyUser> queryWrapper = new LambdaQueryWrapper<>(new SysThirdPartyUser());
        queryWrapper.eq(SysThirdPartyUser::getPlatSource, plat).eq(SysThirdPartyUser::getPlatId, platId).eq(SysThirdPartyUser::getDeleted, Boolean.FALSE);
        ThirdPartyUserVO vo = getVO(queryWrapper);
        return successResponse(vo);
    }

    @Override
    public ResponseData<ThirdPartyUserVO> getThirdInfoByUserId(Long userId, Integer type) {
        SysThirdPartyUser user = this.thirdPartyUserService.lambdaQuery().eq(SysThirdPartyUser::getUserId, userId)
                .eq(SysThirdPartyUser::getPlatSource, type).last("limit 1").one();
        if (user == null)
            return null;
        ThirdPartyUserVO vo = new ThirdPartyUserVO();
        BeanUtils.copyProperties(user, vo);
        return successResponse(vo);
    }

    @Override
    public ResponseData<Boolean> updateThirdInfo(ThirdPartyUserVO thirdPartyUserVO) {
        List<SysThirdPartyUser> list = thirdPartyUserService.lambdaQuery().eq(SysThirdPartyUser::getPlatId, thirdPartyUserVO.getPlatId()).list();
        if (!CollectionUtils.isEmpty(list)) {
            List<SysThirdPartyUser> unionIds = list.stream().filter(item -> StringUtils.hasText(item.getUnionId()) && item.getUnionId().equals(thirdPartyUserVO.getUnionId())).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(unionIds))
                thirdPartyUserService.lambdaUpdate().set(SysThirdPartyUser::getUnionId, thirdPartyUserVO.getUnionId()).eq(SysThirdPartyUser::getPlatId, thirdPartyUserVO.getPlatId()).update();
            return successResponse(Boolean.TRUE);
        }
        return successResponse(Boolean.FALSE, "用户不存在");
    }

    @Override
    public ResponseData<Boolean> addThirdInfo(ThirdPartyUserVO vo) {
        List<SysThirdPartyUser> list = thirdPartyUserService.lambdaQuery().eq(SysThirdPartyUser::getUnionId, vo.getUnionId()).list();
        List<SysThirdPartyUser> exists = list.stream().filter(item -> item.getPlatId().equals(vo.getPlatId())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(exists)) {
            List<SysThirdPartyUser> apps = list.stream().filter(item -> item.getPlatSource().equals(ThirdPartyTypeEnum.WX_APP.getValue())).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(apps)) {
                SysThirdPartyUser thirdPartyUser = new SysThirdPartyUser();
                thirdPartyUser.setDeleted(Boolean.FALSE);
                thirdPartyUser.setCreateTime(new Date());
                thirdPartyUser.setPlatId(vo.getPlatId());
                thirdPartyUser.setUnionId(vo.getUnionId());
                thirdPartyUser.setPlatSource(vo.getPlatSource());
                thirdPartyUser.setUserId(apps.get(0).getUserId());
                thirdPartyUser.setUsername(apps.get(0).getUsername());
                thirdPartyUserService.save(thirdPartyUser);
            }
        }
        return successResponse();
    }

    private ThirdPartyUserVO getVO(LambdaQueryWrapper<SysThirdPartyUser> queryWrapper) {
        List<SysThirdPartyUser> list = thirdPartyUserService.list(queryWrapper);
        if (CollectionUtils.isEmpty(list))
            return null;
        ThirdPartyUserVO vo = new ThirdPartyUserVO();
        BeanUtils.copyProperties(list.get(0), vo);
        return vo;
    }

    /**
     * 公众号服务器配置验证
     */
    @GetMapping("/wxMp")
    public void mpGateWay(HttpServletRequest request, HttpServletResponse response) {
        log.info("微信服务器请求进来了...");
        // 微信加密签名，signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
        String signature = request.getParameter("signature");
        log.info("signature:" + signature);
        // 时间戳
        String timestamp = request.getParameter("timestamp");
        log.info("timestamp:" + timestamp);
        // 随机数
        String nonce = request.getParameter("nonce");
        log.info("nonce:" + nonce);
        // 随机字符串
        String echostr = request.getParameter("echostr");
        log.info("echostr:" + echostr);

        try (PrintWriter out = response.getWriter()) {
            // 通过检验signature对请求进行校验，若校验成功则原样返回echostr，否则接入失败
            log.info("token:" + siteProperties.getToken());
            if (WeiXinMpUtil.checkSignature(signature, timestamp, nonce, siteProperties.getToken())) {
                out.print(echostr);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @PostMapping("/wxMp")
    public void mpMessageGateway(HttpServletRequest request, HttpServletResponse response) {
        // 使用response的时候必须要设置响应头，不然会乱码~~~
        response.setHeader("Content-type", "text/xml;charset=UTF-8");
        ServletInputStream inputStream;
        try {
            inputStream = request.getInputStream();
            String result = new BufferedReader(new InputStreamReader(inputStream))
                    .lines().collect(Collectors.joining(System.lineSeparator()));
            WxMpMessageXmlModel wxMpMessageXmlModel = WeiXinMpUtil.convertToObject(result, WxMpMessageXmlModel.class);
            log.info("微信推送的消息", wxMpMessageXmlModel);
            if ("event".equalsIgnoreCase(wxMpMessageXmlModel.getMsgType())
                    && "subscribe".equalsIgnoreCase(wxMpMessageXmlModel.getEvent())) {
                rabbitTemplate.convertAndSend(WX_MP_CHANNEL, WX_MP_ROUTING, wxMpMessageXmlModel);
                // 发送关注后消息，可以根据业务需要是否发送，该发送直接在当前接口回复
                Map<String, String> parameters = new HashMap<>();
                parameters.put("ToUserName", wxMpMessageXmlModel.getFromUserName());
                parameters.put("FromUserName", wxMpMessageXmlModel.getToUserName());
                parameters.put("CreateTime", (System.currentTimeMillis() / 1000) + "");
                parameters.put("MsgType", "text");
                parameters.put("Content", "终于等到你！感谢你为我而来");
                String xmlResult = WeiXinMpUtil.mapToXml(parameters);
                response.getWriter().write(xmlResult);
                return;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            response.getWriter().write("");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
